package perceptronMathieu;

public class Perceptron
{
	private static final double	biasedValues[]	= { 1.0 };

	private double				inputNodes[];
	private double				hiddenNodes[][];
	private double				outputNodes[];

	private double				hiddenNodesLink[][][];
	private double				outputNodesLink[][];

	public Perceptron(int input, int hiddenLayers, int nodePerLayer, int output)
	{
		inputNodes = new double[input + biasedValues.length];
		hiddenNodes = new double[hiddenLayers][nodePerLayer];
		outputNodes = new double[output];

		hiddenNodesLink = new double[hiddenLayers][nodePerLayer][nodePerLayer];
		outputNodesLink = new double[output][nodePerLayer];

		// The biased nodes' values must be initialized
		for (int i = 0; i < biasedValues.length; ++i)
		{
			inputNodes[inputNodes.length - biasedValues.length + i] = biasedValues[i];
		}

		for (int i = 0; i < hiddenLayers; ++i)
		{
			for (int j = 0; j < nodePerLayer; ++j)
			{
				hiddenNodes[i][j] = 1.0;
			}
		}

		for (int i = 0; i < hiddenNodesLink.length; ++i)
		{
			for (int j = 0; j < hiddenNodesLink[i].length; ++j)
			{
				for (int k = 0; k < hiddenNodesLink[i][j].length; ++k)
				{
					hiddenNodesLink[i][j][k] = 1.0;
				}
			}
		}
		for (int i = 0; i < outputNodesLink.length; ++i)
		{
			for (int j = 0; j < outputNodesLink[i].length; ++j)
			{
				outputNodesLink[i][j] = 1.0;
			}
		}
	}

	// This assumes that there are as many links as nodes
	private double linearActivation(double links[], double nodes[])
	{
		double value = 0.0;

		for (int i = 0; i < nodes.length; ++i)
		{
			value += nodes[i] * links[i];
		}

		return value;
	}

	private double sigmoidActivation(double links[], double nodes[])
	{
		double value = linearActivation(links, nodes);
		value = (1 + Math.tanh(value)) / 2d;

		return value;
	}

	public void activate(double input[])
	{
		setInput(input);
		activate();
	}

	public void activate()
	{
		// activating hidden nodes
		for (int i = 0; i < hiddenNodes.length; ++i)
		{
			for (int j = 0; j < hiddenNodes[i].length; ++j)
			{
				if (i == 0)
				{
					hiddenNodes[i][j] = sigmoidActivation(
							hiddenNodesLink[i][j], inputNodes);
				} else
				{
					hiddenNodes[i][j] = sigmoidActivation(
							hiddenNodesLink[i][j], hiddenNodes[i - 1]);
				}
			}
		}
		// activating output nodes
		for (int i = 0; i < outputNodes.length; i++)
		{
			outputNodes[i] = linearActivation(outputNodesLink[i],
					hiddenNodes[hiddenNodes.length - 1]);
		}
	}

	public void setInput(double input[])
	{
		if (input.length == (inputNodes.length - biasedValues.length))
		{
			for (int i = 0; i < input.length; ++i)
			{
				inputNodes[i] = input[i];
			}
		} else
		{
			System.err
					.print("Cannot set the input, "
							+ "the size of the given array does not match the number of input nodes");
		}
	}

	public void setInput(int position, double value)
	{
		inputNodes[position] = value;
	}

	public void setLinks(double links[])
	{
		if (links.length == getLinksCount())
		{
			int link_pos = 0;
			for (int i = 0; i < hiddenNodesLink.length; ++i)
			{
				for (int j = 0; j < hiddenNodesLink[i].length; ++j)
				{
					for (int k = 0; k < hiddenNodesLink[i].length; ++k, ++link_pos)
					{
						hiddenNodesLink[i][j][k] = links[link_pos];
					}
				}
			}
			for (int i = 0; i < outputNodesLink.length; ++i)
			{
				for (int j = 0; j < outputNodesLink[i].length; ++j, ++link_pos)
				{
					outputNodesLink[i][j] = links[link_pos];
				}
			}
		} else
		{
			// TODO throw an exception
		}
	}

	public int getLinksCount()
	{
		int count = 0;
		count += hiddenNodesLink.length * hiddenNodesLink[0].length
				* hiddenNodesLink[0][0].length;
		count += outputNodesLink.length * outputNodesLink[0].length;

		return count;
	}

	public double[] getOutput()
	{
		return outputNodes;
	}

	public double getOutput(int position)
	{
		return outputNodes[position];
	}

	public String toString()
	{
		String s = System.getProperty("line.separator") + "perceptron : ";

		s += System.getProperty("line.separator") + "inputs = "
				+ this.toString(this.inputNodes);
		s += System.getProperty("line.separator") + "in links = "
				+ this.toString(this.hiddenNodesLink[0]);
		s += System.getProperty("line.separator") + "hidden nodes = "
				+ this.toString(this.hiddenNodes);
		s += System.getProperty("line.separator") + "out links = "
				+ this.toString(this.outputNodesLink);
		s += System.getProperty("line.separator") + "outputs = "
				+ this.toString(this.outputNodes);

		return s;
	}

	private String toString(double[] vector)
	{
		String s = "{";
		for (int i = 0; i < vector.length; i++)
		{
			if (i > 0)
				s += "; ";
			s += vector[i];
		}
		return s += "}";
	}

	private String toString(double[][] matrix)
	{
		String s = "{";
		for (int i = 0; i < matrix.length; i++)
		{
			s += "\n(" + i + ")";
			s += this.toString(matrix[i]);
		}
		return s += "}";
	}

	public static void main(String[] args)
	{
		final int inNodesCount = 2;
		final int hiddenNodesCount = 3;
		final int outNodesCount = 1;
		Perceptron perceptron = new Perceptron(inNodesCount, 1,
				hiddenNodesCount, outNodesCount);
		perceptron.setInput(0, 0.5);
		perceptron.setInput(1, 0.1);

		double links[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 };
		perceptron.setLinks(links);

		perceptron.activate();
		System.out.println(perceptron);
	}
}
